package com.tencent.sr.iris.activity.service.event.job;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.dangdang.ddframe.job.api.ShardingContext;
import com.dangdang.ddframe.job.api.simple.SimpleJob;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.tencent.sr.iris.activity.common.enums.AwardRecordIssueStatusEnum;
import com.tencent.sr.iris.activity.common.enums.TaskBatchDateIsParticipatedEnum;
import com.tencent.sr.iris.activity.common.enums.TaskRecordAwardIssueStatusEnum;
import com.tencent.sr.iris.activity.common.enums.UserTaskStatusEnum;
import com.tencent.sr.iris.activity.common.util.ShardingIdUtil;
import com.tencent.sr.iris.activity.domain.event.dto.ActivityTaskDTO;
import com.tencent.sr.iris.activity.repository.Criteria;
import com.tencent.sr.iris.activity.repository.entity.TIrisActivityAwardRecordDO;
import com.tencent.sr.iris.activity.repository.entity.TIrisUserTaskRecordDO;
import com.tencent.sr.iris.activity.repository.mapper.TIrisUserTaskRecordMapper;
import com.tencent.sr.iris.activity.repository.mapper.example.TIrisUserTaskRecordExample;
import com.tencent.sr.iris.activity.service.activity.ActivityCommonService;
import com.tencent.sr.iris.activity.service.activity.IrisActivityTaskService;
import com.tencent.sr.iris.activity.service.activity.IrisUserActivityRecordService;
import com.tencent.sr.iris.activity.service.activity.IrisUserTaskRecordService;
import com.tencent.sr.iris.activity.service.config.ActivityConfig;
import com.tencent.sr.iris.activity.service.config.TaskAwardStrategyConfig;
import com.tencent.sr.iris.activity.service.event.EventExecutorContext;
import com.tencent.sr.iris.activity.service.event.task.AbstractIrisTaskExecutor;
import com.tencent.sr.iris.activity.service.event.task.UserBecomeTraineeTaskExecutor;
import com.tencent.sr.iris.activity.service.event.util.ActivityCommonUtilService;
import com.tencent.sr.rmall.common.exception.retail.TRetailErrorException;
import com.tencent.sr.rmall.springbootstarter.elasticjob.TaskJob;
import com.tencent.sr.rmall.springbootstarter.monitor.util.MonitorUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * 发奖失败重试job，每天9点30执行一次
 *
 * @author xiaojun
 */
@Slf4j
@Component
@TaskJob(jobName = "userAwardRetryJob", cron = "0 0/20 * * * ?", description = "发奖失败重试")
public class UserAwardRetryJob implements SimpleJob {

    @Resource
    private RedissonClient redissonClient;
    @Resource
    private TIrisUserTaskRecordMapper tIrisUserTaskRecordMapper;
    @Resource
    private IrisActivityTaskService irisActivityTaskService;
    @Resource
    private ActivityCommonService activityCommonService;
    @Resource
    private IrisUserTaskRecordService userTaskRecordService;
    @Resource
    private TaskAwardStrategyConfig taskAwardStrategyConfig;
    @Resource
    private ActivityConfig activityConfig;
    @Resource
    private EventExecutorContext eventExecutorContext;

    @Resource
    private IrisUserActivityRecordService activityRecordService;

    @Resource
    private ActivityCommonUtilService activityCommonUtilService;

    private static final int MAX_SHARDING_ID = 1023;

    /**
     * 分布式锁最大执行时间20分钟
     */
    private static final int MAX_LOCK_TIME = 20;
    /**
     * 分页大小
     */
    private static final int LIMIT = 500;

    @Override
    public void execute(ShardingContext shardingContext) {
        log.info("重试发奖任务：start");
        long startTime = System.currentTimeMillis();
        Throwable throwable = null;
        RLock lock = null;
        try {
            lock = redissonClient.getLock(getLockKey());
            if (!lock.tryLock(MAX_LOCK_TIME, TimeUnit.MINUTES)) {
                log.info("重试发奖任务：有任务正在执行中，本次忽略扫表");
                return;
            }
            Integer awardRetryMaxDays = Optional.ofNullable(activityConfig.getAwardRetryMaxDays()).orElse(30);
            Integer awardRetryMaxLoopTimes = Optional.ofNullable(activityConfig.getAwardRetryMaxLoopTimes()).orElse(10);

            for (int i = 0; i <= MAX_SHARDING_ID; i++) {
                //记录每个shard最大循环次数
                int loopTimes = 0;
                int offset = 0;
                List<TIrisUserTaskRecordDO> failList = null;
                do {
                    loopTimes++;
                    //获取48小时前的时间点
                    Calendar calendar = Calendar.getInstance();
                    calendar.add(Calendar.DATE, -awardRetryMaxDays);
                    Date maxTime = calendar.getTime();

                    failList = queryNeedRetryRecords(null, i, offset, LIMIT, maxTime);
                    if (CollectionUtil.isNotEmpty(failList)) {
                        failList.stream().forEach(failRecord -> doRetry(failRecord));
                    }
                    offset += LIMIT;
                } while (CollectionUtil.isNotEmpty(failList) && failList.size() == LIMIT && loopTimes < awardRetryMaxLoopTimes);
            }
        } catch (Exception e) {
            log.error("重试发奖任务：执行异常", e);
            throwable = e;
        } finally {
            if (lock != null) {
                lock.unlock();
            }
            log.info("重试发奖任务：end");
            MonitorUtil.getSingleMonitorService().logTransaction("activity_job", "userAwardRetryJob",
                    startTime, throwable);
        }
    }

    /**
     * 查询需要重试发奖的记录
     *
     * @param shardingId
     * @param offset
     * @param limit
     * @param maxTime
     * @return
     */
    private List<TIrisUserTaskRecordDO> queryNeedRetryRecords(String uid, int shardingId, int offset, int limit,
            Date maxTime) {
        List<TIrisUserTaskRecordDO> failList;
        TIrisUserTaskRecordExample example = new TIrisUserTaskRecordExample();
        //非注册有礼活动
        Criteria criteria = example.createCriteria();
        criteria.andPropertyEqualTo(TIrisUserTaskRecordExample.SHARDING_ID, shardingId)
                .andPropertyEqualTo(TIrisUserTaskRecordExample.USER_TASK_STATUS,
                        UserTaskStatusEnum.FINISHED.getCode())
                .andPropertyNotEqualTo(TIrisUserTaskRecordExample.ACTIVITY_ID, activityConfig.getRegistGiftActivityId())
                .andPropertyEqualTo(TIrisUserTaskRecordExample.AWARD_ISSUE_STATUS,
                        TaskRecordAwardIssueStatusEnum.ISSUE_FAILED.getCode())
                .andPropertyEqualTo(TIrisUserTaskRecordExample.IS_PARTICIPATED,
                        TaskBatchDateIsParticipatedEnum.YES.getCode())
                .andPropertyGreaterThan(TIrisUserTaskRecordExample.CREATE_TIME, maxTime)
                .andPropertyEqualTo(TIrisUserTaskRecordExample.IS_PARTICIPATED,
                        TaskBatchDateIsParticipatedEnum.YES.getCode())
                .andPropertyEqualTo(TIrisUserTaskRecordExample.STATUS, 0);
        if (!Strings.isNullOrEmpty(uid)) {
            criteria.andPropertyEqualTo(TIrisUserTaskRecordExample.UID, uid);
        }

        //注册有礼活动
        Criteria criteria2 = example.or();
        criteria2.andPropertyEqualTo(TIrisUserTaskRecordExample.SHARDING_ID, shardingId)
                .andPropertyEqualTo(TIrisUserTaskRecordExample.USER_TASK_STATUS,
                        UserTaskStatusEnum.FINISHED.getCode())
                .andPropertyEqualTo(TIrisUserTaskRecordExample.ACTIVITY_ID, activityConfig.getRegistGiftActivityId())
                .andPropertyEqualTo(TIrisUserTaskRecordExample.TASK_PERIOD, LocalDate.now().toString())
                .andPropertyEqualTo(TIrisUserTaskRecordExample.AWARD_ISSUE_STATUS,
                        TaskRecordAwardIssueStatusEnum.ISSUE_FAILED.getCode())
                .andPropertyGreaterThan(TIrisUserTaskRecordExample.CREATE_TIME, maxTime)
                .andPropertyEqualTo(TIrisUserTaskRecordExample.STATUS, 0);
        if (!Strings.isNullOrEmpty(uid)) {
            criteria2.andPropertyEqualTo(TIrisUserTaskRecordExample.UID, uid);
        }

        example.setOrderByClause("create_time desc");

        failList = tIrisUserTaskRecordMapper.limitSelectByExample(example, offset, limit);
        return failList;
    }

    private void doRetry(TIrisUserTaskRecordDO userTaskRecordDO) {
        AbstractIrisTaskExecutor taskHandler = (AbstractIrisTaskExecutor) eventExecutorContext.getTaskHandler(userTaskRecordDO.getTaskId());
        if (taskHandler == null) {
            log.error("找不到对应的任务执行器，任务信息：{}", JSON.toJSONString(userTaskRecordDO));
            return;
        }
        RLock lock = null;
        try {
            String lockKey = taskHandler.getLockKey(userTaskRecordDO.getUid(), userTaskRecordDO.getActivityId(),
                    userTaskRecordDO.getTaskId(), Optional.ofNullable(userTaskRecordDO.getTaskPeriod()).orElse(StrUtil.EMPTY));
            lock = redissonClient.getLock(lockKey);
            if (lock == null || !lock.tryLock(10, TimeUnit.SECONDS)) {
                log.error("重试发奖加锁失败，任务信息：{}", JSON.toJSONString(userTaskRecordDO));
                return;
            }

            String taskBizId = userTaskRecordDO.getTaskId();
            ActivityTaskDTO taskDTO = irisActivityTaskService.getTaskByTaskBizId(taskBizId);

            //注册有礼发奖重试单独处理
            if (activityConfig.getRegistGiftActivityId().equals(userTaskRecordDO.getActivityId())) {
                UserBecomeTraineeTaskExecutor userBecomeTraineeTaskExecutor = (UserBecomeTraineeTaskExecutor) taskHandler;
                userBecomeTraineeTaskExecutor.executeAward(taskDTO, Lists.newArrayList(), userTaskRecordDO);
                log.info("注册有礼重试发奖任务正常，uid:{},userTaskRecord bizId:{}", userTaskRecordDO.getUid(),
                        userTaskRecordDO.getBizId());
                activityRecordService.addOrUpdateActivityRecord(userTaskRecordDO.getUid(), userTaskRecordDO.getCurrentTaskStartTime().getTime(),
                        userTaskRecordDO.getActivityId(), userTaskRecordDO.getPeriodType(), userTaskRecordDO.getTaskPeriod());
                return;
            }

            RLock rLock = null;
            try {
                rLock = activityCommonUtilService.inviteGiftTryLock(userTaskRecordDO.getActivityId(), userTaskRecordDO.getPid(), userTaskRecordDO.getUid());

                TIrisActivityAwardRecordDO awardRecordDO = null;
                if (taskAwardStrategyConfig.getSendAwardForUserTaskIds().contains(taskBizId)) {
                    awardRecordDO = activityCommonService.sendAward(userTaskRecordDO, taskDTO);
                } else {
                    awardRecordDO = activityCommonService.sendAwardForSupervisor(userTaskRecordDO, taskDTO);
                }
                //记录任务进度
                userTaskRecordService.updateUserTaskRecordStatus(userTaskRecordDO, awardRecordDO);
                activityRecordService.addOrUpdateActivityRecord(userTaskRecordDO.getUid(), userTaskRecordDO.getCurrentTaskStartTime().getTime(), userTaskRecordDO.getActivityId(),
                        userTaskRecordDO.getPeriodType(), userTaskRecordDO.getTaskPeriod());
                log.info("重试发奖任务正常，uid:{},userTaskRecord bizId:{},发奖结果:{}", userTaskRecordDO.getUid(),
                        userTaskRecordDO.getBizId(), AwardRecordIssueStatusEnum.getByCode(awardRecordDO.getIssueStatus()));
            } catch (Exception e) {
                log.error("邀请有礼事件重试失败：", e);
                throw new TRetailErrorException(e);
            } finally {
                if (rLock != null && rLock.isLocked() && rLock.isHeldByCurrentThread()) {
                    rLock.unlock();
                }
            }
        } catch (Exception e) {
            log.error("重试发奖任务异常：uid:{},userTaskRecord bizId:{}", userTaskRecordDO.getUid(), userTaskRecordDO.getBizId(),
                    e);
        } finally {
            if (lock != null && lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    private String getLockKey() {
        return "userAwardRetryJob_lock";
    }


    /**
     * 根据uid重试发奖失败，每次最多重试20条
     *
     * @param uid
     * @return
     */
    public Integer doRetryByUid(String uid) {
        //获取48小时前的时间点
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, -30);
        Date maxTime = calendar.getTime();

        List<TIrisUserTaskRecordDO> list = queryNeedRetryRecords(uid, ShardingIdUtil.doSharding(uid), 0, LIMIT,
                maxTime);
        if (CollectionUtil.isEmpty(list)) {
            return 0;
        }
        list.stream().forEach(record -> {
            doRetry(record);
        });
        return list.size();
    }

}
